﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace DarwinIsDead {

    /// <summary>
    /// Base class for any thing in the game that "exists" in the game World.
    /// Specifically, an Entity is any object that
    /// <li>Can be updated every logic frame</li>
    /// <li>Has a position (x, y) in the game World</li>
    /// <li>May have box-based collision detection</li>
    /// <li>May be drawn to the screen.</li>
    /// Player, Bullet, and Zombie each publically inherit Entity.
    /// </summary>
    public abstract class Entity : IDisposable {
        // Position
        private Vector2 position;
        // Previous x position for movement interpolation
        private float prevX;
        // Previous y position for movement interpolation
        private float prevY;

        // Collision hitbox
        public Rectangle HitBox;
        public Color Color;

        protected int Round(float n) {
            return (int)(n + 0.5f);
        }

        /// <summary>
        /// Reference to the <see cref="World"/> this <see cref="Entity"/>
        /// exists in.  Use this to access the Game object and other Entities.
        /// </summary>
        protected World World {
            get;
            private set;
        }

        /// <summary>
        /// Construct an <see cref="Entity"/> in a World with the specified
        /// hit box dimensions.
        /// </summary>
        /// <param name="World">Reference to the World this exists in</param>
        /// <param name="x">x-coordinate of the upper left-hand corner of this
        /// Entity's bounding box</param>
        /// <param name="y">y-coordinate of the upper right-hand corner of this
        /// Entity's bounding box</param>
        /// <param name="w">Width of this Entity's bounding box</param>
        /// <param name="h">Height of this Entity's bounding box</param>
        /// <param name="solid">True if this Entity has collision</param>
        public Entity(World World, float x=0f, float y=0f, float w=0f,
            float h=0f, bool solid=false) {            
            this.World = World;

            position = new Vector2(x, y);
            HitBox = new Rectangle(Round(x), Round(y), Round(w), Round(h));
            Destroy = false;
            Solid = solid;
            prevX = x;
            prevY = y;
        }

        /// <summary>
        /// Construct an <see cref="Entity"/> in a World with the specified
        /// hit box dimensions.
        /// </summary>
        /// <param name="World">Reference to the World this Entity exists
        /// in</param>
        /// <param name="rect">Hit box dimensions used for collision
        /// detection</param>
        /// <param name="solid">True if this Entity has collision</param>
        public Entity(World World, Rectangle rect, bool solid=false) {
            if (rect != null) {
                position = new Vector2(rect.X, rect.Y);
                HitBox = rect;
                prevX = rect.X;
                prevY = rect.Y;
            } else {
                position = Vector2.Zero;
                HitBox = Rectangle.Empty;
                prevX = 0;
                prevY = 0;
            }
            Destroy = false;
            Solid = solid;
        }

        /// <summary>
        /// Construct an <see cref="Entity"/> in a World with the specified
        /// hit box dimensions.
        /// </summary>
        /// <param name="World">Reference to the World this Entity exists
        /// in</param>
        /// <param name="pos">Entity's position</param>
        /// <param name="size">Entity's size</param>
        public Entity(World World, Vector2 pos, Vector2 size, bool Solid=false) {
            HitBox = new Rectangle();
            position = new Vector2();
            if (pos != null) {
                Position = pos;
                HitBox.X = (int)pos.X;
                HitBox.Y = (int)pos.Y;
                prevX = pos.X;
                prevY = pos.Y;
            } else {
                Position = Vector2.Zero;
            }

            if (size != null) {
                HitBox.Width = (int)size.X;
                HitBox.Height = (int)size.Y;
            }
            else {
                HitBox.Width = HitBox.Height = 0;
            }

            Destroy = false;
            this.Solid = Solid;
        }

        /// <summary>
        /// Releases unmanaged resources we want to explicitly handle,
        /// rather than leaving it to the garbage collector.
        /// </summary>
        public virtual void Dispose() {
        }

        /// <summary>
        /// Marks this Entity for destruction.  The World this Entity
        /// belongs to will remove it from the environment on the next
        /// logic cycle.
        /// </summary>
        public bool Destroy {
            get;
            set;
        }

        /// <summary>
        /// The x and y-coordinates of this Entity in the World.  Use this
        /// attribute directly to change the Entity's position without invoking
        /// position interpolation or collision detection.  If you want these
        /// features, use the <see cref="SetPosition"/> and <see cref="Move"/>
        /// methods instead.
        /// </summary>
        public Vector2 Position {
            get { return position; }
            set {
                prevX = value.X;
                prevY = value.Y;

                position.X = value.X;
                position.Y = value.Y;

                HitBox.X = Round(position.X);
                HitBox.Y = Round(position.Y);
            }
        }

        /// <summary>
        /// Returns true if <see cref="Entity"/> has collision detection,
        /// false otherwise.
        /// </summary>
        public bool Solid {
            get;
            set;
        }

        /// <summary>
        /// Depth factor of this <see cref="Entity"/>.  Used to sort 
        /// </summary>
        public int Depth {
            get;
            set;
        }

        /// <summary>
        /// Sets this <see cref="Entity"/>'s position.
        /// 
        /// Invokes linear position interpolation, which allows collision
        /// checks in intermediate points between positions.  This gives
        /// the illusion of continuous movement, and prevents objects from
        /// accidentally passing through each other.
        /// </summary>
        /// <seealso cref="SetPosition(Vector2)"/>
        /// <seealso cref="Move(float, float)"/>
        /// <seealso cref="Move(Vector2)"/>
        /// <param name="x">New x-coordinate</param>
        /// <param name="y">New y-coordinate</param>
        public virtual void SetPosition(float x, float y) {
            prevX = position.X;
            prevY = position.Y;

            position.X = x;
            position.Y = y;

            HitBox.X = Round(x);
            HitBox.Y = Round(y);
        }

        /// <summary>
        /// Sets this <see cref="Entity"/>'s position.
        /// 
        /// Invokes linear position interpolation, which allows collision
        /// checks in intermediate points between positions.  This gives
        /// the illusion of continuous movement, and prevents objects from
        /// accidentally passing through each other.
        /// </summary>
        /// <seealso cref="SetPosition(float, float)"/>
        /// <seealso cref="Move(float, float)"/>
        /// <seealso cref="Move(Vector2)"/>
        /// <param name="pos">New position</param>
        public virtual void SetPosition(Vector2 pos) {
            prevX = position.X;
            prevY = position.Y;

            position.X = pos.X;
            position.Y = pos.Y;

            HitBox.X = Round(pos.X);
            HitBox.Y = Round(pos.Y);
        }

        /// <summary>
        /// Offset this <see cref="Entity"/>'s position.
        /// 
        /// Invokes linear position interpolation, which allows collision
        /// checks in intermediate points between positions.  This gives
        /// the illusion of continuous movement, and prevents objects from
        /// accidentally passing through each other.
        /// </summary>
        /// <seealso cref="Move(Vector2)"/>
        /// <seealso cref="SetPosition(float, float)"/>
        /// <seealso cref="SetPosition(Vector2)"/>
        /// <param name="dx">x-coordinate offset</param>
        /// <param name="dy">y-coordinate offset</param>
        public virtual void Move(float dx, float dy) {
            prevX = position.X;
            prevY = position.Y;

            position.X += dx;
            position.Y += dy;

            HitBox.X = Round(position.X);
            HitBox.Y = Round(position.Y);
        }

        /// <summary>
        /// Offset this <see cref="Entity"/>'s position.
        /// 
        /// Invokes linear position interpolation, which allows collision
        /// checks in intermediate points between positions.  This gives
        /// the illusion of continuous movement, and prevents objects from
        /// accidentally passing through each other.
        /// </summary>
        /// <seealso cref="Move(float, float)"/>
        /// <seealso cref="SetPosition(float, float)"/>
        /// <seealso cref="SetPosition(Vector2)"/>
        /// <param name="dx">x-coordinate offset</param>
        /// <param name="dy">y-coordinate offset</param>
        public virtual void Move(Vector2 dp) {
            prevX = position.X;
            prevY = position.Y;

            position.X += dp.X;
            position.Y += dp.Y;

            HitBox.X = Round(position.X);
            HitBox.Y = Round(position.Y);
        }

        /// <summary>
        /// Checks the collision between this <see cref="Entity"/>'s hit box
        /// and a specified rectangle.
        /// 
        /// Takes position interpolation into account, checking intermediate
        /// positions between the Entity's current and previous position so
        /// that there is an illusion of continuous movement.  This prevents
        /// objects from passing through each other without a collision
        /// occurring.
        /// </summary>
        /// <param name="other"></param>
        /// <returns>The overlapping rectangle in the collision if one occurs,
        /// or <see cref="Rectangle.Empty"/> otherwise</returns>
        public virtual Rectangle Collides(Rectangle other) {
            // First, see if it's worth our time to do an interpolated check
            if (Math.Abs(position.X - prevX) <= HitBox.Width &&
                Math.Abs(position.Y - prevY) <= HitBox.Height) {
                return Rectangle.Intersect(HitBox, other);
            } else {
                int px = Round(prevX);
                int py = Round(prevY);

                // Check for collisions at width/ehgith intervals along the
                // line
                Rectangle check =
                    new Rectangle(px, py, px + HitBox.Width,
                        py + HitBox.Height);

                Debug.WriteLine(this.GetType() + " " + check);

                // y = mx + b
                float m = (HitBox.Y - prevY) / (HitBox.X - prevX);
                int b = (int)(-m * px) + py;

                int div  = Math.Min(HitBox.Width, HitBox.Height);
                int l = HitBox.X - px;

                int lsign = Math.Sign(l);

                for (int i = 0; i < ((lsign * l) / div); i++) 
                {
                    int dx = px + (lsign * i * div);
                    check.X += dx;
                    check.Y += (int)(m * dx + b);
                    return Rectangle.Intersect(check, other);
                }
            }

            return Rectangle.Empty;
        }

        /// <summary>
        /// Collision behavior, called when a collision between this
        /// <see cref="Entity"/> and another Entity occurs.  Override this
        /// to specify behaviors based on collision events.
        /// 
        /// When overriding, try to only change this Entity's state.  Avoid
        /// changing the other's state if possible.
        /// </summary>
        /// <param name="other">Reference to the other Entity in the collision
        /// </param>
        /// <param name="overlap">Overlap between this Entity and other's hit
        /// boxes</param>
        public virtual void Collision(Entity other, Rectangle overlap) {
        }

        /// <summary>
        /// Logical update, called once per logic frame.  This is where the
        /// Entity's internal state is changed.  All usable Entity-derived
        /// classes must override this.
        /// </summary>
        /// <param name="gameTime">Delta time</param>
        public virtual void Update(GameTime gameTime) {
        }

        /// <summary>
        /// Renders this <see cref="Entity"/>.  All draws to the provided
        /// <see cref="SpriteBatch"/> in this method are done relative to
        /// the World.
        /// </summary>
        /// <param name="spriteBatch">SpriteBatch to draw to</param>
        public virtual void Draw(SpriteBatch spriteBatch) {
            
        }

        /// <summary>
        /// Renders relative to the screen.  Draw any UI elements here.
        /// </summary>
        /// <param name="spriteBatch">SpriteBatch to draw to</param>
        public virtual void DrawUI(SpriteBatch spriteBatch) {
        }
    }
}
